Skip to content

🤖 feat: add Mux Extension Platform v1#3255

Draft
ThomasK33 wants to merge 174 commits into
mainfrom
extensions-main
Draft

🤖 feat: add Mux Extension Platform v1#3255
ThomasK33 wants to merge 174 commits into
mainfrom
extensions-main

Conversation

@ThomasK33
Copy link
Copy Markdown
Member

@ThomasK33 ThomasK33 commented May 8, 2026

Summary

Adds the Mux Extension Platform v1 behind the EXTENSION_PLATFORM experiment and documents the platform's architectural pivot to Extension Modules: extension folders with a single extension.ts, statically extractable manifests, QuickJS-based discovery/activation, skill-style root precedence, source locks, and Mux-owned trust/capability state. The implementation is now partially refactored toward that model: trusted roots can discover direct child Extension Module folders via static extension.ts manifest extraction, local authoring roots use ~/.mux/extensions/local, and source-lock schemas now model git/vendored extension sources without carrying trust state.

Background

This PR grew out of the need to consolidate Mux's extension surfaces across skills, tools, agents, policies, themes, and future runtime contributions. During review, the design moved away from npm-package identity and repo-stored project approvals toward a Go-modules-like Extension Module model. The updated docs capture that decision, the code hardens the current scaffold so repositories cannot provide security authority, and the latest slices begin moving discovery/root layout/source metadata from package manifests to static Extension Module manifests and locks.

Implementation

  • Adds extension manifest/domain schemas, descriptor validation, permission calculation, drift detection, conflict resolution, provenance-aware telemetry helpers, global state, project-scoped state, and snapshot caching.
  • Adds transitional Extension Name support alongside old dotted package identities so kebab-case module names can appear in registry snapshots, IPC payloads, and persisted state keys.
  • Adds static extension.ts manifest extraction for export const manifest = defineManifest({ ... }) or a literal object export, rejecting dynamic manifest values without executing extension code.
  • Adds trusted-root Extension Module discovery for direct child folders containing extension.ts, including folder-name validation, manifest.name mismatch diagnostics, project-local pre-trust no-read behavior, and static capability validation.
  • Points the user-global local authoring root at ~/.mux/extensions/local and updates initializeUserRoot to create that folder instead of a package-root package.json.
  • Adds source-lock schemas for global git locks and project git/vendored locks, with strict rejection of repository-provided trust/approval state.
  • Adds node discovery/registry services, root watching, bundled-root assembly, ORPC routes, service-container wiring, and bun run debug extensions.
  • Threads extension-contributed skills through skill discovery, slash dispatch, stream context construction, and agent_skill_* tool reads, including hardened skill-body reads that reject symlinks and TOCTOU path swaps.
  • Adds the Extensions settings UI, Extension cards, consent shortcut/granular controls, diagnostics surfacing, palette entries, and keyboard shortcuts.
  • Adds a bundled demo extension package and deterministic bundled-extension assembly tooling for the current scaffold.
  • Updates docs, RFC, PRD, ADRs, release checklist, telemetry docs, and authoring docs to describe Extension Modules (extension.ts) as the target architecture.
  • Stores project-local extension trust/enablement/grants in Mux-owned global storage under ~/.mux/extensions/project-state/<project-hash>/, not inside the target repository.

Validation

  • make static-check
  • make test -j1
  • Latest TDD slices:
    • bun test src/node/extensions/extensionRoots.test.ts src/node/orpc/extensionsRouter.test.ts src/common/extensions/sourceLocks.test.ts src/node/extensions/staticManifestExtractor.test.ts src/node/extensions/extensionDiscoveryService.test.ts
  • Earlier targeted tests during review cleanup:
    • bun test src/common/extensions/conflictResolver.test.ts src/common/extensions/permissionCalculator.test.ts
    • bun test src/node/extensions/bundledExtensionsAssemble.test.ts
    • bun test src/node/extensions/projectExtensionStateService.test.ts
    • bun test src/node/orpc/extensionsRouter.test.ts
    • bun test src/cli/debug/extensions.test.ts
    • bun test src/browser/features/Settings/Sections/ExtensionCard.test.tsx src/browser/features/Settings/Sections/ExtensionsSection.test.tsx

Risks

This is a large additive subsystem touching startup wiring, settings UI, package assembly, telemetry, and skill discovery. The primary rollback lever is the default-on EXTENSION_PLATFORM experiment. The highest remaining architectural risk is that full QuickJS Registration Discovery/Full Activation and git install/store materialization are still follow-up work; the current module-discovery slice intentionally publishes no module-registered skills until that runtime path exists.

Pains

This PR required several review and merge cycles: resolving older security findings, integrating concurrent main changes around heartbeat/image-generation skill filtering, aligning extension skill IDs with agent skill schemas, moving project extension state out of repositories after review identified the trust-injection vulnerability, and beginning the package-to-Extension-Module refactor while preserving transitional compatibility.


Generated with mux • Model: openai:gpt-5.5 • Thinking: off • Cost: $916.09

ThomasK33 added 30 commits May 8, 2026 10:44
Capture the extension platform glossary, ADRs, and PRD generated from the design grilling session.

---

_Generated with `mux` • Model: `openai:gpt-5.5` • Thinking: `high` • Cost: `729323{MUX_COSTS_USD:-0}`_

<!-- mux-attribution: model=openai:gpt-5.5 thinking=high costs=103.38 -->
Apply six P0 fixes from advisor review and capture supporting language in CONTEXT.md and a new ADR-0005:

- Add v1 Contribution Activation Matrix (separates schema-supported from capability-consumed; clarifies that themes/layouts/runtime presets/commands are inspection-only in v1).
- Mark inspection-only contribution types as Provisional Descriptors so their schemas can evolve without bumping descriptor version.
- Tighten telemetry boundary to Provenance-gated Telemetry (reserved-prefix regex AND bundled-root provenance), with defense-in-depth.
- Make Snapshot Cache feed only the Inspection Path; Capability Path uses the live Snapshot, and cache invalidation includes Global/Project-local Extension State mtimes.
- Clarify Grant Records store the normalized granted set (inferred registration + operational); enablement does not grant registration.
- Drop the manifest icon field; inspection UI uses generic icons.
- Add P0 Acceptance Criteria section before Implementation Decisions.
- Make IPC mutators identify with { rootId, extensionId } and add BundledExtensionRootResolver as a supporting module.
- Replace bun install --no-save assembly with deterministic offline copy/pack.
- Pre-trust project-local discovery is existence-only.
- Add explicit security tests (reserved prefix, provenance-gated telemetry, capability-vs-cache separation, drift across new contribution types) and screenshot/video evidence requirements to dogfood checklist.
- Add ADR-0005 capturing the aggregate security boundary.

---

_Generated with `mux` • Model: `anthropic:claude-opus-4-7` • Thinking: `max` • Cost: `881861{MUX_COSTS_USD:-0}`_

<!-- mux-attribution: model=anthropic:claude-opus-4-7 thinking=max costs=130.54 -->
…r kill switch

ServiceContainer now resolves the bundled extension root via
detectBundledExtensionRoot() (preferring the assembled tree at build/extensions
in dev) and kicks off an initial extensions.reload() so the Settings UI paints
on cold start without a hang. SettingsPage gates the Extensions tab on the
EXTENSION_PLATFORM experiment so the kill switch hides the section without
unmounting downstream sections. Adds the e2e Electron smoke that asserts the
Demo Extension card surfaces on first paint, disappears with the kill switch,
survives a renderer reload, and reappears on re-enable without a fresh
trust/grant prompt.
…cklist)

Adds the v1 authoring quickstart + manifest reference, the full v1 telemetry
events catalog with provenance-gating notes, and the pre-release dogfood
checklist with screenshot/video evidence requirements. Wires the new pages
into docs.json navigation and regenerates the built-in mux-docs index so the
docs skill surfaces them.
Formatting-only normalization (collapse multi-line argument lists, consistent
quoting) across the extension layer plus minor test cleanup left over from
US-026/27/28 integration. Marks US-026/27/28 passes:true in tasks/prd.json.
PRD §Permission Model defines bundled Extensions as policy-granted (not
user-consented). Before this fix the Demo Extension shipped with
`granted: false`, surfaced `Pending re-grant` on the card, and the
`mux-extensions` skill never reached `Available` — breaking the
"fresh-install, no manual setup" promise of P0 #2.

Discovery now treats `isBundled` as activation-granted, and the Registry
synthesizes a matching policy Grant Record at permission-calculation time
(never persisted, recomputed on every reload, distribution-identity-aligned
so drift stays `null` across version bumps). The discovery test that
asserted `activated:false` on a bundled root without a grant has been
updated to reflect the new contract.
…ry disabled

Renderer's useExperimentValue falls back to `enabledByDefault` when
PostHog returns no assignment, but the backend's isExperimentEnabled
returned `false` in the same scenario — so dev-server / MUX_E2E builds
shipped with EXTENSION_PLATFORM (default-on) effectively off. Frontend
showed the Extensions tab while the backend reported "No extension roots
configured".

isExperimentEnabled now uses EXPERIMENTS[id].enabledByDefault as the
fallback. Existing tests for default-off experiments still assert false
because their definitions are enabledByDefault:false.
…ively

The renderer can't reproduce permissionCalculator's canonical SHA-256
without a Node `crypto` import, so both Consent Shortcut paths sent
`requestedPermissionsHash: ""`. After persistence the very next reload
read `hash !== ""` → driftStatus `permissions-changed` for an
already-fresh grant.

setGrant now overwrites the hash with hashRequestedPermissions(<live
manifest's requestedPermissions>) before persistence; falls back to
hashing grantedPermissions when no live snapshot is available (equivalent
under v1's all-or-nothing grants). The frontend-side comments are updated
to point at the canonical recompute site instead of misleading future
readers.
#2)

agentSkillsService had zero references to extensionRegistry. Even with the
Demo Extension fully Available in the Registry snapshot, its
`mux-extensions` skill never reached the slash menu — directly
contradicting PRD P0 #2 ("Demo Extension visible end-to-end … exposes
the mux-extensions skill via the existing slash menu").

Changes:
- Adds 'extension' as a fourth AgentSkillScope value (project > global >
  extension > built-in precedence so user-authored skills always shadow
  extension-provided ones).
- ExtensionRegistry.getSkillSources() returns the resolved list of
  Available skill contributions (absolute body path + display metadata)
  for agentSkillsService to consume.
- discoverAgentSkills / discoverAgentSkillsDiagnostics / readAgentSkill
  accept an optional extensionSkills array; the agentSkills router
  supplies it from context.extensionRegistry.getSkillSources() on every
  call so kill-switch flips and reload events take effect immediately.
- readAgentSkill reads the extension body via node:fs (extensions live on
  the host, not the workspace runtime) and returns it with
  scope:'extension'.
- SkillIndicator gains an "Extension" scope group; MuxMessageMetadata
  scope union widens to include 'extension' so /skill messages from
  extension skills render correctly.
- Adds YAML frontmatter to packages/mux-extension-platform-demo/SKILL.md
  so parseSkillMarkdown accepts it (the Manifest Validator only needs the
  body file to exist; the agent-skill consumer needs frontmatter).
The 'Why?' link in the Inferred Registration Permissions header pointed at
docs.mux.dev (not our domain) with an anchor that didn't exist. Repoints
to https://mux.coder.com/extensions/authoring#permissions where the v1
permissions reference actually lives.
The Shift+? hotkey hint at the top of the Extensions Settings Section was
rendered as low-contrast plain text on a transparent ghost button — hard
to read against the section background. Wraps the keybind in the same
<kbd> styling already used by ExtensionsCheatSheetModal so the trigger
visually echoes the cheat sheet itself instead of looking like a comment.
The modal panel was styled with `bg-background-primary`, but
globals.css only defines `--color-background` and
`--color-background-secondary`. The non-existent token resolved to
nothing, so the panel rendered fully transparent — only the dimmed
backdrop was visible behind it, and the shortcut list bled through
into the section content. Switches to `bg-background-secondary` to
match ConsentShortcutModal.
…atform-demo

The package shipped under `@mux/extension-platform-demo`, but `@mux` is
not Coder's npm scope. Renames the distribution identity to
`@coder/mux-extension-platform-demo` (Extension Identity `mux.platformdemo`
is unchanged — it lives under the reserved `mux.*` prefix and survives
package renames per ADR-0003).

Touches: package.json + README + SKILL.md inside the workspace, the
docs/extensions/authoring.mdx quickstart, bundled-extensions.ts comment,
test fixtures (the assemble + discovery + registry suites all rebuilt
against the new path), and the PRD/CONTEXT/prd.json planning artifacts
that referenced the old name. Regenerates the embedded mux-docs builtin
skill index so `agent_skill_read` surfaces the renamed link.
agentSkillsService.list (the slash menu) merged extension-contributed
skills correctly, but the dispatch path that actually invokes a skill
(`agentSession.ts` slash resolver, `agent_skill_read`,
`agent_skill_read_file`) called readAgentSkill without the
extensionSkills source list. Result: the slash menu showed
`mux-extensions` but `/mux-extensions` failed with
`Agent skill not found: mux-extensions` — the user-reported bug.

Wires a single getExtensionSkillSources provider from
ExtensionRegistry.getSkillSources() through three layers:
- ServiceContainer registers the provider with both WorkspaceService
  (for AgentSession.sendMessage slash resolution) and AIService (for
  the tool layer).
- WorkspaceService passes the provider into every AgentSession via a
  new option.
- AgentSession reads the live source list per slash invocation; fixes
  agentSession.ts:5253 to pass extensionSkills to readAgentSkill.
- AIService injects extensionSkills into ToolConfiguration on every
  stream so agent_skill_read / agent_skill_read_file resolve extension
  skills the same way they resolve project / global / built-in skills.

Adds a regression test in agentSession.agentSkillSnapshot.test.ts that
exercises a slash invocation against an extension-contributed skill —
fails on the pre-fix codepath, passes here.
@ThomasK33
Copy link
Copy Markdown
Member Author

@codex review

Addressed the latest findings:

  • corrupted/non-directory active vendored paths are treated as stale and rebuilt;
  • module watchers are constrained to the module root plus the conventional skills subtree instead of every nested directory.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7e686362d8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/extensions/projectExtensionSourceSync.ts
@ThomasK33
Copy link
Copy Markdown
Member Author

@codex review

Addressed the active-root self-heal gap: non-directory/ELOOP project active roots are now removed as stale before lock sync continues, with regression coverage.

@chatgpt-codex-connector
Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Nice work!

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ThomasK33
Copy link
Copy Markdown
Member Author

@codex review

Pushed an empty retry commit after the Unit job hit an unrelated React act/concurrent-render flake; no code changes since your last approval.

@chatgpt-codex-connector
Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 👍

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ThomasK33
Copy link
Copy Markdown
Member Author

@codex review

Pushed a test-harness fix for the Unit CI failure: the analytics hook tests now restore the previous global window/document instead of clearing them for subsequent UI tests. Targeted UI subset and static-check pass locally.

@chatgpt-codex-connector
Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Can't wait for the next one!

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ThomasK33
Copy link
Copy Markdown
Member Author

@codex review

Pushed a narrower DOM isolation fix for the Unit CI failure: analytics hook tests now use the shared UI installDom helper so they restore the baseline Happy DOM environment needed by later UI tests.

@chatgpt-codex-connector
Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Delightful!

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ThomasK33
Copy link
Copy Markdown
Member Author

@codex review

Follow-up for Unit CI: removed the per-test analytics DOM install/restore entirely. The analytics tests now only import the shared baseline DOM bootstrap, avoiding async global window/document races with other UI test files.

@chatgpt-codex-connector
Copy link
Copy Markdown

Codex Review: Didn't find any major issues. What shall we delve into next?

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ThomasK33
Copy link
Copy Markdown
Member Author

@codex review

Pushed a shared test DOM recovery fix: tests/ui/dom now exposes ensureDomInstalled() and reinstalls the baseline if older hook tests clear window/document after the module bootstrap has already run. The failing hooks/UI order and static-check pass locally.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6cc6596a77

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/browser/hooks/useAnalytics.test.tsx Outdated
@ThomasK33
Copy link
Copy Markdown
Member Author

@codex review

Addressed the DOM isolation review: analytics and extension palette hook tests now use per-test installDom()/cleanup restoration again, while the shared dom helper only self-heals if an older test had already cleared the baseline before the per-test snapshot. The failing hook/UI order and static-check pass locally.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6a428ea29d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/extensions/extensionRoots.ts Outdated
@ThomasK33
Copy link
Copy Markdown
Member Author

@codex review

Addressed the active-source integrity finding: roots now verify the materialized active source tree still matches the project lock content hashes before skipping sync, so tampered active views trigger re-sync even when the lockfile fingerprint is unchanged.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8292afdcfb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/extensions/extensionRoots.ts Outdated
@ThomasK33
Copy link
Copy Markdown
Member Author

@codex review

Addressed the validation failure path: active-source validation is now inside the same guarded project-lock sync path, so malformed locks or validation errors downgrade the project root to inspectable/non-activating instead of escaping roots enumeration or startup.

@chatgpt-codex-connector
Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Keep it up!

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant